1 /*
2  * Hunt - a framework for web and console application based on Collie using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.text.configuration;
13 
14 import std.traits;
15 import std.conv;
16 import std.exception;
17 import std.array;
18 import std.stdio;
19 import std.string;
20 import kiss.logger;
21 
22 class ConfFormatException : Exception
23 {
24 	mixin basicExceptionCtors;
25 }
26 
27 class NoValueHasException : Exception
28 {
29 	mixin basicExceptionCtors;
30 }
31 
32 class Configuration
33 {
34 	class ConfigurationValue
35 	{
36 		@property value(string name){
37 			auto v =  _map.get(name, null);
38 			enforce!NoValueHasException(v,format(" %s is not in config! ",name));
39 			return v;
40 		}
41 
42 		@property value(){
43 			return _value;
44 		}
45 		
46 		auto opCast(T)(){
47                     static if(is(T == bool))
48                         return as!bool(true);
49                     else static if(isSomeString!T)
50                         return cast(T)(value());
51                     else static if(isNumeric!(T))
52                         return as!T(T.init);
53                     else
54                         static assert(0,"not support type");
55 		}
56 
57 		auto as(T)(T value = T.init) if(isNumeric!(T))
58 		{
59 			if(_value.length == 0)
60 				return value;
61 			else
62 				return to!T(_value);
63 		}
64 		
65 		auto as(T : bool)(T value = T.init)
66 		{
67 			if(_value.length == 0 || _value == "false" || _value == "0")
68 				return false;
69 			else
70 				return true;
71 		}
72 
73 		auto as(T : string)(T value = T.init)
74 		{
75 			if(_value.length == 0)
76 				return value;
77 			else
78 				return _value;
79 		}
80 
81 		auto opDispatch(string s)()
82 		{
83 			return value(s);
84 		}
85 		
86 	private :
87 		string _value;
88 		ConfigurationValue[string] _map;
89 	}
90 	
91 	this(string filename, string section = "")
92 	{
93 		_section = section;
94 		loadConfig(filename);
95 	}
96 	
97 	@property value(string name){
98 		return _value.value(name);
99 	}
100 	
101 	auto opDispatch(string s)()
102 	{
103 		return _value.opDispatch!(s)();
104 	}
105 	
106 private:
107 	void loadConfig(string filename)
108 	{
109 		_value = new ConfigurationValue();
110 
111 		import std.file;
112 		if(!exists(filename))
113 		{	
114 			throw new Exception("The config file doesn't exist: " ~ filename);
115 		}
116 		import std.format;
117 		auto f = File(filename,"r");
118 		if(!f.isOpen()) return;
119 		scope(exit) f.close();
120 		string section = "";
121 		int line = 1;
122 		while(!f.eof())
123 		{
124 			scope(exit) line += 1;
125 			string str = f.readln();
126 			str = strip(str);
127 			if(str.length == 0) continue;
128 			if(str[0] == '#' || str[0] == ';') continue;
129 			auto len = str.length -1;
130 			if(str[0] == '[' && str[len] == ']')
131 			{
132 				section = str[1..len].strip;
133 				continue;
134 			}
135 			if(section != _section && section != "")
136 				continue;// 不是自己要读取的分段,就跳过
137 			auto site = str.indexOf("=");
138 			enforce!ConfFormatException((site > 0),format("the format is erro in file %s, in line %d",filename,line));
139 			string key = str[0..site].strip;
140 			setValue(split(key,'.'),str[site + 1..$].strip);
141 		}
142 	}
143 	
144 	void setValue(string[] list, string value)
145 	{
146 		auto cvalue = _value;
147 		foreach(ref str ; list){
148 			if(str.length == 0) continue;
149 			auto tvalue = cvalue._map.get(str,null);
150 			if(tvalue is null){ // 不存在就追加一个
151 				tvalue = new ConfigurationValue();
152 				cvalue._map[str] = tvalue;
153 			}
154 			cvalue = tvalue;
155 		}
156 		if(cvalue is _value)
157 			return;
158 		cvalue._value = value;
159 	}
160 	
161 private:
162 	string _section;
163 	ConfigurationValue _value;
164 }
165 
166 
167 unittest
168 {
169 	import std.stdio;
170 
171 	import FE = std.file;
172 
173 	FE.write("test.config","http.listen = 100 \napp.test =  \n# this is  \n ; start dev\n [dev]\napp.test = dev");
174 	auto conf = new Configuration("test.config");
175 	assert(conf.http.listen.as!long() == 100);
176 	assert(conf.app.test.value() == "");
177 	
178 	auto confdev = new Configuration("test.config","dev");
179 	long tv = cast(long)confdev.http.listen;
180 	assert(tv == 100);
181 	assert(confdev.http.listen.as!long() == 100);
182 	writeln("----------" ,confdev.app.test.value());
183 
184 	string tvstr = cast(string)confdev.app.test;
185 	//auto tvstrw = cast(wstring)confdev.app.test;
186 
187 	assert(tvstr == "dev");
188 	assert(confdev.app.test.value() == "dev");
189 	bool tvBool = cast(bool)confdev.app.test;
190 	assert(tvBool);
191 
192 	string str;
193 	auto e = collectException!NoValueHasException(confdev.app.host.value(), str);
194 	assert(e && e.msg == " host is not in config! ");
195 
196 }